using System;
using UnityEditor.Graphs;
using UnityEditor.ShaderGraph;
using UnityEditor.ShaderGraph.Serialization;
using UnityEngine;

namespace UnityEditor.Graphing
{
    class HandleUndoRedoAction : IGraphDataAction
    {
        void HandleGraphUndoRedo(GraphData graphData)
        {
            AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out HandleUndoRedoAction");
            AssertHelpers.IsNotNull(newGraphData, "NewGraphData is null while carrying out HandleUndoRedoAction");
            try
            {
                graphData?.ReplaceWith(newGraphData);
            }
            catch (Exception e)
            {
                if (graphData != null)
                    graphData.replaceInProgress = false;
                throw e;
            }
        }

        public Action<GraphData> modifyGraphDataAction => HandleGraphUndoRedo;

        public GraphData newGraphData { get; set; }
    }

    class GraphObject : ScriptableObject, ISerializationCallbackReceiver
    {
        [SerializeField]
        SerializationHelper.JSONSerializedElement m_SerializedGraph;

        [SerializeField]
        int m_SerializedVersion;

        [SerializeField]
        bool m_IsDirty;

        [SerializeField]
        bool m_IsSubGraph;

        [SerializeField]
        string m_AssetGuid;

        internal string AssetGuid
        {
            get => m_AssetGuid;
        }

        [NonSerialized]
        GraphData m_Graph;

        [NonSerialized]
        int m_DeserializedVersion;

        public DataStore<GraphData> graphDataStore
        {
            get => m_DataStore;
            private set
            {
                if (m_DataStore != value && value != null)
                    m_DataStore = value;
            }
        }

        DataStore<GraphData> m_DataStore;

        public GraphData graph
        {
            get { return m_Graph; }
            set
            {
                if (m_Graph != null)
                    m_Graph.owner = null;
                m_Graph = value;
                graphDataStore = new DataStore<GraphData>(ReduceGraphDataAction, m_Graph);
                if (m_Graph != null)
                    m_Graph.owner = this;
            }
        }

        Material m_MaterialArtifact;

        /// <summary>
        /// The Material artifact generated by the import process.
        /// Every modification to this material will be lost when the graph is saved.
        /// for.
        /// </summary>
        public Material materialArtifact
        {
            get
            {
                if (m_MaterialArtifact == null)
                    m_MaterialArtifact = AssetDatabase.LoadAssetAtPath<Material>(AssetDatabase.GUIDToAssetPath(AssetGuid));
                return m_MaterialArtifact;
            }
        }

        // this value stores whether an undo operation has been registered (which indicates a change has been made to the graph)
        // and is used to trigger the MaterialGraphEditWindow to update it's title
        public bool isDirty
        {
            get { return m_IsDirty; }
            set { m_IsDirty = value; }
        }

        public virtual void RegisterCompleteObjectUndo(string actionName)
        {
            Undo.RegisterCompleteObjectUndo(this, actionName);
            if (materialArtifact)
                Undo.RecordObject(m_MaterialArtifact, actionName);
            m_SerializedVersion++;
            m_DeserializedVersion++;
            m_IsDirty = true;
        }

        public void OnBeforeSerialize()
        {
            if (graph != null)
            {
                var json = MultiJson.Serialize(graph);
                m_SerializedGraph = new SerializationHelper.JSONSerializedElement { JSONnodeData = json };
                m_IsSubGraph = graph.isSubGraph;
                m_AssetGuid = graph.assetGuid;
            }
        }

        public void OnAfterDeserialize()
        {
        }

        public bool wasUndoRedoPerformed => m_DeserializedVersion != m_SerializedVersion;

        public void HandleUndoRedo()
        {
            Debug.Assert(wasUndoRedoPerformed);
            var deserializedGraph = DeserializeGraph();

            var handleUndoRedoAction = new HandleUndoRedoAction();
            handleUndoRedoAction.newGraphData = deserializedGraph;
            graphDataStore.Dispatch(handleUndoRedoAction);
        }

        GraphData DeserializeGraph()
        {
            var json = m_SerializedGraph.JSONnodeData;
            var deserializedGraph = new GraphData { isSubGraph = m_IsSubGraph, assetGuid = m_AssetGuid };
            MultiJson.Deserialize(deserializedGraph, json);
            m_DeserializedVersion = m_SerializedVersion;
            m_SerializedGraph = default;
            return deserializedGraph;
        }

        public void Validate()
        {
            if (graph != null)
            {
                graph.OnEnable();
                graph.ValidateGraph();
            }
        }

        // This is a very simple reducer, all it does is take the action and apply it to the graph data, which causes some mutation in state
        // This isn't strictly redux anymore but its needed given that our state tree is quite large and we don't want to be creating copies of it everywhere by unboxing
        void ReduceGraphDataAction(GraphData initialState, IGraphDataAction graphDataAction)
        {
            graphDataAction.modifyGraphDataAction(initialState);
        }

        void OnEnable()
        {
            if (graph == null && m_SerializedGraph.JSONnodeData != null)
            {
                graph = DeserializeGraph();
            }
            Validate();
        }

        void OnDestroy()
        {
            graph?.OnDisable();
        }
    }
}
